//-----------------------------------------------------------------------------
// Filename: SIPUDPChannel.cs
//
// Description: SIP transport for UDP.
// 
// History:
// 17 Oct 2005	Aaron Clauson	Created.
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2006 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------

using System;
using System.Collections;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BlueFace.Sys;
using BlueFace.Sys.Net;
using BlueFace.VoIP.Net;
using log4net;

#if UNITTEST
using NUnit.Framework;
#endif

namespace BlueFace.VoIP.Net.SIP
{
	public class SIPUDPChannel : SIPChannel
	{
		private ILog logger = AppSpecificState.logger;

		// Channel sockets.
		private UdpClient m_sipConn = null;
		private bool m_closed = false;

		public SIPUDPChannel(IPEndPoint endPoint)
		{	
			try
			{
                m_localEndPoint = endPoint;
                m_sipProtocol = SIPProtocolsEnum.UDP;
                m_sipConn = new UdpClient(endPoint);

                Thread listenThread = new Thread(new ThreadStart(Listen));
                listenThread.Start();

				logger.Info("SIPUDPChannel listener created " + endPoint + ".");
			}
			catch(Exception excp)
			{
				logger.Error("Exception SIPUDPChannel (ctor). " + excp.Message);
				throw excp;
			}
		}

		public void Dispose(bool disposing)
		{
			try
			{
				this.Close();
			}
			catch(Exception excp)
			{
				logger.Error("Exception Disposing SIPUDPChannel. " + excp.Message);
			}
		}
				
		private void Listen()
		{
			try
			{
				IPEndPoint inEndPoint = new IPEndPoint(IPAddress.Any, 0);
				byte[] buffer = null;

                logger.Debug("SIPUDPChannel socket on " + m_localEndPoint + " listening started.");

				while(!m_closed)
				{
                    try
                    {
                        buffer = m_sipConn.Receive(ref inEndPoint);
                    }
                    catch (SocketException)
                    {
                        // ToDo. Pretty sure these exceptions get thrown when an ICMP meesage comes back indicating there is no listening
                        // socket on the other end. It would be nice to be able to relate that back to the socket that the data was sent to
                        // so that we know to stop sending.
                        //logger.Error("SocketException SIPUDPChannel (" + sockExcp.ErrorCode + "). " + sockExcp);
                        
                        inEndPoint = new IPEndPoint(IPAddress.Any, 0);
                        continue;
                    }
                    catch(Exception listenExcp)
                    {
                        // There is no point logging this as without processing the ICMP message it's not possible to know which socket the rejection came from.
                        logger.Error("Exception listening on SIPUDPChannel. " + listenExcp);
                        
                        inEndPoint = new IPEndPoint(IPAddress.Any, 0);
                        continue;
                    }

					if(buffer == null || buffer.Length == 0)
					{
                        // No need to care about zero byte packets.
                        //string remoteEndPoint = (inEndPoint != null) ? inEndPoint.ToString() : "could not determine";
                        //logger.Error("Zero bytes receveid on SIPUDPChannel on " + m_localEndPoint + " from " + remoteEndPoint  + ".");
					}
					else
					{
						if(SIPMessageReceived != null)
						{
							SIPMessageReceived(this, inEndPoint, buffer);
						}
					}
				}

                logger.Debug("SIPUDPChannel socket on " + m_localEndPoint + " listening halted.");
			}
			catch(Exception excp)
			{
				logger.Error("Exception SIPUDPChannel Listen. " + excp.Message);
				//throw excp;
			}
		}

        public override void Send(IPEndPoint destinationEndPoint, string message)
        {
            byte[] messageBuffer = Encoding.UTF8.GetBytes(message);
            Send(destinationEndPoint, messageBuffer);
        }

		public override void Send(IPEndPoint destinationEndPoint, byte[] buffer)
		{
            try
            {
                if (destinationEndPoint == null)
                {
                    throw new ApplicationException("An empty destination was specified to Send in SIPUDPChannel.");
                }
                else
                {
                    m_sipConn.Send(buffer, buffer.Length, destinationEndPoint);
                }
            }
            catch (ApplicationException appExcp)
            {
                throw appExcp;
            }
            catch (ObjectDisposedException)
            {
                logger.Warn("The SIPUDPChannel was not accessible when attempting to send a message to, " + IPSocket.GetSocketString(destinationEndPoint) + ".");
            }
            catch (Exception excp)
            {
                logger.Error("Exception (" + excp.GetType().ToString() + ") SIPUDPChannel Send (sendto=>" + IPSocket.GetSocketString(destinationEndPoint) + "). " + excp.Message);
                throw excp;
            }
		}

        public override void Send(IPEndPoint destinationEndPoint, byte[] buffer, bool reconnect)
        {
            Send(destinationEndPoint, buffer);
        }

		public override void Close()
		{
			try
			{
				logger.Debug("Closing SIP UDP Channel " + SIPChannelEndPoint + ".");
				
				m_closed = true;
				m_sipConn.Close();
			}
			catch(Exception excp)
			{
				logger.Warn("Exception SIPUDPChannel Close. " +excp.Message);
			}
		}

		#region Unit testing.

		#if UNITTEST
	
		[TestFixture]
		public class SIPRequestUnitTest
		{
			[TestFixtureSetUp]
			public void Init()
			{
				
			}

			[TestFixtureTearDown]
			public void Dispose()
			{			
				
			}

			[Test]
			public void SampleTest()
			{
				Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name);
				
				Assert.IsTrue(true, "True was false.");
			}
		}

		#endif

		#endregion
	}
}
